En este report nos enfocaremos en definir una estrategia de trading para el SP500; desarrollaremos por lo tanto modelos predictivos, en este caso de clasificación (sólo estamos interesados en el signo del return, no en el valor concreto que toma).
Nos quedaremos tan sólo con las observaciones posteriores al 1 de Diciembre de 1969, para evitar valores ausentes y demás. Es posible que más adelante decidamos quedarnos con un rango de datos aún inferior, pues es muy probable que los patrones de movimientos del precio de hace 40 años y los de ahora no tengan nada que ver (los mercados han evolucionado, los agentes del mercado han cambiado, las dinámicas internacionales entre los países también, la normativa etc.)
Las variables de que se compone nuestro dataset son:
Como vemos las correlaciones entre algunas de nuestras variables son altas; sin embargo el gráfico superior puede resultar engañoso, pues tenemos cada fila correspondiente al mismo momento temporal \(t\), mientras que nosotros queremos modelizar \(r_{t+1} = \beta_i \cdot X_t\) (en realidad vamos a modelizar solo el signo de \(r_{t+1}\)), siendo \(X_t\) las variables que elijamos para predecir dicho signo. Por este motivo, “recortaremos” nuestro dataset, para traer la variable CRSP_SPvwx una observación hacia atrás (haciendo así que el TARGET, que pertenece al momento \(t+1\) esté en la misma fila que las variables pertenecientes al momento \(t\)).
Ahora volvemos a realizar el gráfico de correlaciones, pero esta vez tenemos el TARGET de la forma que hemos especificado en el párrafo anterior, de tal forma que podemos ver la relación lineal que existe entre las variables predictoras en el momento \(t\) y la variable objetivo en el momento \(t+1\).
Como vemos, la correlación del TARGET en \(t+1\) con el resto de variables en \(t\) no es alta; solo las variables corpr, ltr, svar y csp tienen una correlación distinta de 0 con el TARGET, y ni siquiera se puede decir que ésta sea significativa.
Comenzaremos a crear algunas variables que en la literatura financiera, en especial en este link se menciona que son significativas a la hora de predecir el return del día siguiente.
Ahora volveremos a hacer el gráfico de correlaciones con estas nuevas variables que hemos introducido.
No parecemos encontrar variables que de verdad tenga una correlación alta, ya sea positiva o negativa, con el signo del precio del mes siguiente. Esto podría ser preocupante; vamos a realizar ahora un análisis algo más fino, en el que podamos encontrar relaciones entre las diferentes variables que teníamos más las que hemos creado y el TARGET.
Vemos que la variable csp tiene valores ausentes a partir del 2000 y algo, por lo que no creemos que sea buena idea utilizar esta variable. De hecho, hacemos un pequeño estudio de las variables que tienen valores ausentes y esto es lo que encontramos:
## date Index D12 E12 b.m tbl
## 0 0 0 0 0 0
## AAA BAA lty ntis Rfree infl
## 0 0 0 0 0 0
## ltr corpr svar csp CRSP_SPvw CRSP_SPvwx
## 0 0 0 167 0 0
## TARGET dpt dyt ept det
## 0 0 0 0 0
Como podemos apreciar en este gráfico, hay dos o tres épocas de especial volatilidad en los mercados; estos son momentos pertenecientes a épocas de crisis. Podríamos incluir una variable binaria que capturara el efecto de la crisis; pero esto sería sesgar el modelo, porque en un setting real, no vamos a saber que estamos en crisis hasta que hayamos acumulado unas cuantas pérdidas en el mercado por caídas inesperadas grandes de las que éste no se recupera rápidamente.
Para continuar con el análisis, el TARGET lo vamos a transformar ya a 0 y 1 (baja o sube), de tal forma que podamos ver si ambas clases están balanceadas, además de examinar alguna posible relación con las variables predictivas que tenemos.
d2$TARGET = ifelse(d2$TARGET > 0, 1, 0)Vemos que las clases están desbalanceadas, pues hay más días que el mercado experimenta subidas que bajadas. Esto podría sesgar al modelo, pues va a tener una tasa de error relativamente baja apostando siempre a que sube, pero un modelo de este tipo evidentemente perdería dinero. Por lo tanto tendremos que tener en cuenta esta característica de nuestro TARGET a la hora de modelizar, para equilibrar un poco más las clases. Utilizaremos además métricas como el AUC o F1, que son mucho más adecuadas que el Accuracy cuando tenemos clases desbalanceadas.
Aunque las diferencias no son excesivamente grandes, sí podemos ver que para corpr más grandes, generalmente hay más valores de 1 que de 0. Esto, de todas formas, ya decimos que es muy pequeño; a partir de 0.05 vemos algunas observaciones más en 1 que en 0, pero no suficiente como para que un clasificador pueda predecir decentemente.
En el caso de ltr, observamos algo parecido a lo que podíamos ver en el anterior gráfico de corpr, y es que para \(ltr \geq 0.05\) hay más oservaciones con TARGET = 1 que con TARGET = 0. En este caso hay aún más ruido que en corpr.
Por último, vamos a ver la relación entre el TARGET y svar, ya que ésta es la otra variable que tiene una \(\lvert \rho_{x, TARGET}} \geq 0.10\).
Parece que cuando la varianza es muy alta, al mes siguiente generalmente el mercado cae (TARGET = 0).
Vamos a crear un par de variables más, igual que hacen los autores del artículo mencionado anteriormente. En la primera de las mismas vamos a restar al long-term yield on government bonds el treasury bill rate; en el segundo caso, queremos obtener la diferencia entre el AAA rate y el BAA rate (el rate medio que tienen cada uno de estos grupos, según nivel de riesgo en deuda). En el chunk inferior se puede ver el código utilizado para crear estas dos variables.
d2$tms = d2$lty-d2$tbl
d2$dfy = log(d2$BAA/d2$AAA)
cor(d2$dfy, d2$TARGET)## [1] 0.04114294
cor(d2$tms, d2$TARGET)## [1] 0.06996692
Como vemos, las correlaciones de estas dos variables nuevas que hemos creado no son demasiado altas. Hemos probado tanto la opción del logaritmo de la división como la resta simple en ambos casos, quedándonos con la opción que mejor correlación dejaba en cada caso.
## date Index D12 E12
## Min. :1970-02-01 Min. : 63.54 Min. : 3.070 Min. : 5.13
## 1st Qu.:1981-10-08 1st Qu.: 125.68 1st Qu.: 6.566 1st Qu.: 14.18
## Median :1993-06-16 Median : 449.16 Median :12.520 Median : 22.41
## Mean :1993-06-16 Mean : 699.14 Mean :14.724 Mean : 35.04
## 3rd Qu.:2005-02-22 3rd Qu.:1206.16 3rd Qu.:20.160 3rd Qu.: 51.35
## Max. :2016-11-01 Max. :2198.81 Max. :45.476 Max. :105.96
##
## b.m tbl AAA BAA
## Min. :0.1205 Min. :0.00010 Min. :0.03280 Min. :0.04220
## 1st Qu.:0.2862 1st Qu.:0.01920 1st Qu.:0.05650 1st Qu.:0.06765
## Median :0.3834 Median :0.04965 Median :0.07550 Median :0.08375
## Mean :0.4948 Mean :0.04839 Mean :0.07706 Mean :0.08806
## 3rd Qu.:0.7144 3rd Qu.:0.06815 3rd Qu.:0.08930 3rd Qu.:0.10200
## Max. :1.2065 Max. :0.16300 Max. :0.15490 Max. :0.17180
##
## lty ntis Rfree
## Min. :0.01750 Min. :-0.057677 Min. :8.333e-06
## 1st Qu.:0.04883 1st Qu.:-0.002242 1st Qu.:1.600e-03
## Median :0.06810 Median : 0.011725 Median :4.137e-03
## Mean :0.06918 Mean : 0.009217 Mean :4.033e-03
## 3rd Qu.:0.08425 3rd Qu.: 0.025070 3rd Qu.:5.679e-03
## Max. :0.14820 Max. : 0.045747 Max. :1.358e-02
##
## infl ltr corpr
## Min. :-0.019153 Min. :-0.112400 Min. :-0.094900
## 1st Qu.: 0.001247 1st Qu.:-0.011425 1st Qu.:-0.008075
## Median : 0.002965 Median : 0.007150 Median : 0.007000
## Mean : 0.003311 Mean : 0.007219 Mean : 0.007299
## 3rd Qu.: 0.005208 3rd Qu.: 0.024850 3rd Qu.: 0.022750
## Max. : 0.018059 Max. : 0.152300 Max. : 0.156000
##
## svar csp CRSP_SPvw
## Min. :0.0001503 Min. :-0.00417 Min. :-0.215795
## 1st Qu.:0.0008340 1st Qu.:-0.00192 1st Qu.:-0.015624
## Median :0.0013608 Median :-0.00130 Median : 0.012023
## Mean :0.0023867 Mean :-0.00115 Mean : 0.009375
## 3rd Qu.:0.0023532 3rd Qu.:-0.00054 3rd Qu.: 0.037569
## Max. :0.0709451 Max. : 0.00235 Max. : 0.168113
## NA's :167
## CRSP_SPvwx TARGET dpt dyt
## Min. :-0.217944 Min. :0.0000 Min. :-4.524 Min. :-4.531
## 1st Qu.:-0.018527 1st Qu.:0.0000 1st Qu.:-3.961 1st Qu.:-3.954
## Median : 0.009288 Median :1.0000 Median :-3.574 Median :-3.571
## Mean : 0.006810 Mean :0.5943 Mean :-3.616 Mean :-3.610
## 3rd Qu.: 0.035542 3rd Qu.:1.0000 3rd Qu.:-3.279 3rd Qu.:-3.279
## Max. : 0.164814 Max. :1.0000 Max. :-2.753 Max. :-2.751
##
## ept det tms dfy
## Min. :-4.836 Min. :-1.2442 Min. :-0.03650 Min. :0.06831
## 1st Qu.:-3.095 1st Qu.:-0.9672 1st Qu.: 0.01090 1st Qu.:0.09887
## Median :-2.868 Median :-0.8461 Median : 0.02245 Median :0.12292
## Mean :-2.831 Mean :-0.7847 Mean : 0.02078 Mean :0.14207
## 3rd Qu.:-2.509 3rd Qu.:-0.6320 3rd Qu.: 0.03150 3rd Qu.:0.16659
## Max. :-1.899 Max. : 1.3795 Max. : 0.04550 Max. :0.51241
##
Aquí podemos ver que casi todas nuestras variables están en una escala más o menos parecida. Además, en el summary de arriba se ven otras cosas como que el 59.43% de los returns son positivos. Ya habíamos comentado antes el tema del desbalanceo, pero no le habíamos puesto cifra.
En el caso de TMS si que somos capaces de ver alguna pequeña relación con el TARGET, en el sentido de que es algo más alto en general cuando el TARGET es 1 que cuando el TARGET es 0. En el caso de Difference Yield Spread (dfy) vemos algún efecto también en la misma dirección.
En el gráfico de arriba podemos ver por último la distribución de todas nuestras variables. Vemos que algunas se comportan más o menos como una Normal, con algo más de curtosis quizás como en el caso de CRSP_SPvwx, y otras parecen tener dos “cabezas”, como dos grupos con un bajón en los valores del centro.
Para seleccionar las variables con las que nos quedamos haremos primero un modelo de Regresión Logística con todas las variables y veremos cuáles son significativas. Antes partiremos los datos en Train y Test.
Fase de Train: 1970-02-01 / 2005-12-01
Fase de Test: 2006-01-01 / 2016-11-01
Al ser datos en el tiempo, no podemos utilizar métodos de validación cruzada como tal, pues ésta coge datos aleatoriamente y nuestros datos han de estar ordenados para poder predecir. La única forma de hacer validación cruzada con series temporales es hacer diferentes particiones pero ordenadas, e ir entrenando el modelo y prediciendo en el siguiente espacio temporal. Sin embargo en este caso, como veremos más adelante, lo que haremos será ajustar el modelo en una parte de train inicial y luego iremos corriendo esa ventana a lo largo del periodo de test, de tal manera que predices el TARGET y automáticamente recibes ese nuevo TARGET (como si estuvieras prediciendo siempre lo que va a ocurrir el próximo mes y en ese nuevo mes vuelves a ajustar el modelo y predecir de nuevo).
Vamos a escalar todas las variables (excepto la fecha), pues esto puede ayudar al modelo. Para ello utilizaremos la función \(MinMaxScaler = \frac{x - min(x)}{max(x) - min(x)}\). Esta función deja todos los valores entre 0 y 1.
##
## Call:
## glm(formula = as.factor(TARGET) ~ ., family = binomial(link = "logit"),
## data = train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.4352 -1.1584 0.7024 0.9929 2.0712
##
## Coefficients: (3 not defined because of singularities)
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -12.40385 13.94699 -0.889 0.3738
## Index 0.94887 4.85512 0.195 0.8451
## D12 1.29642 6.65961 0.195 0.8457
## E12 2.51919 5.15852 0.488 0.6253
## b.m -4.17250 2.44839 -1.704 0.0883 .
## tbl 0.01261 1.63516 0.008 0.9938
## AAA -22.33287 21.95441 -1.017 0.3090
## BAA 25.14942 19.85560 1.267 0.2053
## lty -7.49936 5.54966 -1.351 0.1766
## ntis 0.35913 0.80676 0.445 0.6562
## Rfree NA NA NA NA
## infl -3.59773 1.56456 -2.300 0.0215 *
## ltr -2.65929 2.49061 -1.068 0.2856
## corpr 1.44928 2.71613 0.534 0.5936
## svar -2.60174 2.75037 -0.946 0.3442
## CRSP_SPvw 4.18052 22.65608 0.185 0.8536
## CRSP_SPvwx 20.37103 32.75652 0.622 0.5340
## dpt 123.32018 115.41957 1.068 0.2853
## dyt -117.06827 116.09307 -1.008 0.3133
## ept 2.91671 4.94902 0.589 0.5556
## det NA NA NA NA
## tms NA NA NA NA
## dfy -4.72476 6.64442 -0.711 0.4770
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 585.07 on 430 degrees of freedom
## Residual deviance: 541.49 on 411 degrees of freedom
## AIC: 581.49
##
## Number of Fisher Scoring iterations: 4
Parece que la única variable que es significativa (aparte del Intercept) es la inflación. Nos dará de todas formas un poco igual si es o no significativa estadísticamente la variable, lo que queremos es maximizar el accuracy y, más concretamente, medidas como el AUC o F1. Seguiremos dándole alguna vuelta más a los datos para limpiarles un poco más el ruido antes de comenzar a modelizar. Nos aparecen NAs, lo cual puede indicar que tenemos un problema de singularidad de la matriz, según vemos aquí. Esto es lo que podría estar causando que salgan valores ausentes, y lo que indica es que tenemos mucha colinearidad. Ya vimos en los gráficos de correlación iniciales que había correlaciones altas entre algunas variables, de tal forma que éstas podrían contener información redundante, y el modelo no es capaz de ajustarse con la matriz entera.
Comprobamos mediante el error que nos da el cálculo del VIF, que tenemos multicolinearidad perfecta. Para solventar esto, vamos a utilizar step wise logistic regression, de tal forma que seleccionaremos sólo algunas de las variables, quitándonos las que peor AIC dejen al ser introducidas. Esperamos que esto elimine las variables demasiado correlacionadas. Antes de comenzar a hacer esto, haremos un último cambio en nuestro dataset. Vamos a meter el lag 1, lag 2 y lag 3. También añadiremos la inflación del período lag1, y el b.m ratio (estaba cerca de ser significativa en la regresión logística).
lag1 = c(NA, NA, NA)
lag2 = c(NA, NA, NA)
lag3 = c(NA, NA, NA)
bm1 = c(NA, NA, NA)
infl1 = c(NA, NA, NA)
for(i in 4:nrow(d2)){
lag1 = c(lag1, d2[i-1, "CRSP_SPvwx"])
lag2 = c(lag2, d2[i-2, "CRSP_SPvwx"])
lag3 = c(lag3, d2[i-3, "CRSP_SPvwx"])
bm1 = c(bm1, d2[i-1, 'b.m'])
infl1 = c(infl1, d2[i-1, "infl"])
}
d2$lag1 = lag1
d2$lag2 = lag2
d2$lag3 = lag3
d2$bm1 = bm1
d2$infl1 = infl1
d3 = d2[4:nrow(d2), ]Ampliamos un mes nuestro train y se lo quitamos al test, en compensación por las tres filas perdidas para introducir los lags.
Ahora procedemos a hacer step-wise logistic regression:
##
## Call:
## glm(formula = TARGET ~ E12 + b.m + BAA + lty + infl + ltr + dpt,
## family = binomial(link = "logit"), data = train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.5181 -1.1367 0.7304 0.9928 2.1639
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 1.435 1.061 1.352 0.176230
## E12 5.314 1.404 3.784 0.000155 ***
## b.m -3.245 1.077 -3.013 0.002586 **
## BAA 6.754 2.976 2.269 0.023260 *
## lty -10.656 3.568 -2.986 0.002826 **
## infl -3.364 1.455 -2.312 0.020777 *
## ltr -1.972 1.067 -1.848 0.064553 .
## dpt 7.971 1.550 5.141 2.73e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 581.54 on 428 degrees of freedom
## Residual deviance: 540.63 on 421 degrees of freedom
## AIC: 556.63
##
## Number of Fisher Scoring iterations: 4
Como vemos, ha sido una forma de limpiar un poco el rudio, pues ahora tenemos variables claramente sinificativas en un modelo mucho más reducido libre de overfitting y de multicolinearidad. Podemos usar otro tipo de AIC (antes utilizamos el de ambas direcciones); por ejemplo:
library(MASS)
nullmod = glm(TARGET ~1, family = binomial(link='logit'), data = train)
stepmod = fullmod %>% stepAIC(scope=list(fullmod, nullmod), trace = FALSE, direction = 'backward')
summary(stepmod)##
## Call:
## glm(formula = TARGET ~ E12 + b.m + BAA + lty + infl + ltr + dpt,
## family = binomial(link = "logit"), data = train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -2.5181 -1.1367 0.7304 0.9928 2.1639
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) 1.435 1.061 1.352 0.176230
## E12 5.314 1.404 3.784 0.000155 ***
## b.m -3.245 1.077 -3.013 0.002586 **
## BAA 6.754 2.976 2.269 0.023260 *
## lty -10.656 3.568 -2.986 0.002826 **
## infl -3.364 1.455 -2.312 0.020777 *
## ltr -1.972 1.067 -1.848 0.064553 .
## dpt 7.971 1.550 5.141 2.73e-07 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 581.54 on 428 degrees of freedom
## Residual deviance: 540.63 on 421 degrees of freedom
## AIC: 556.63
##
## Number of Fisher Scoring iterations: 4
Vemos que los resultados son exactamente iguales que cuando usábamos ambas direcciones, por lo tanto parece que este es el modelo con el que empezaremos. Posteriormente valoraremos incluir alguna otra variable. Resulta especialmente llamativo que no se encuentren conexiones entre los lags 1, 2 y 3 (y 4, realmente son 4 porque el lag1 ya estaba incluido, pues recordemos que el TARGET lo trajimos una observación hacia arriba, de esta forma dejando como variable predictiva el Return en t) del Return y el TARGET.
Una forma alternativa para el feature selection es utilizando la librería caret:
Según lo que vemos, el RMSE de media bajaría en la validación cruzada al incluir todas las variables. Planteamos por tanto dos modelos, el que nos sugiere el Step-Wise y el que nos sugiere el método de caret. A este método de caret de todas formas le quitaremos algunas de las variables para evitar problemas graves de colinearidad perfecta.
y_true = test$TARGET
train$TARGET = as.factor(train$TARGET)
test$TARGET = as.factor(test$TARGET)
mod1 = TARGET ~ Index + b.m + tbl + ntis + infl + ltr + svar + CRSP_SPvwx + dpt + BAA + lty + lag1 + lag2 + lag3
mod2 = TARGET ~ E12 + b.m + BAA + lty + infl + ltr + dptCrearemos un dataframe en el que ir guardando los resultados en términos para cada uno de los métodos que utilicemos con cada modelo, para así más adelante poder comparar los resultados. La medida que utilizaremos será \(F1 = \frac{2 \cdot precision \cdot recall}{precision + recall}\), pues es una buena medida para comparar modelos. Para ello utilizaremos el paquete MLmetrics. Nota: Cuando tengamos una clase que no ha sido predicha nunca, el F1 Score nos dará NaN, ya que habrá divisiones entre 0 en su cálculo y eso da NaN; probablemente más adelante tengamos que transformar estos valores a 0 directamente para poder realizar comparaciones.
Rectificación: Desistimos de usar F1_Score por el alto número de errores que da. Estaría bien utilizarla para comparar modelos pues es una buena medida comparativa, pero como en la mayoría de modelos vemos que la clase 0 apenas se predice (lo cual causa los errores de cálculo que mencionaba antes), consideramos que es mejor utilizar el Kappa, pues es un valor que podemos obtener para todos los modelos y de esta forma podemos compararlos mejor. Decidimos utilizar Kappa porque es una medida que contrasta el Accuracy obtenido por el modelo con el que tendría la “predicción tonta”, por eso creemos que para este problema en concreto puede funcionar bastante bien; además siempre tenemos un valor de la misma, aunque sea de 0, por lo que no nos encontraremos con los problemas planteados por la métrica F1. Cuanto más grande sea el Kappa, mejor el modelo.
Sobra decir que esta medida de error se calcula en el test, nos da igual lo bien que se ajuste en el train, lo que queremos es maximizar lo bien que predice los datos que aún no ha visto para poder así diseñar una estrategia exitosa en bolsa.
## Linear Discriminant Analysis
##
## 429 samples
## 14 predictor
## 2 classes: '0', '1'
##
## Pre-processing: centered (14), scaled (14)
## Resampling: Cross-Validated (5 fold, repeated 10 times)
## Summary of sample sizes: 343, 343, 344, 342, 344, 344, ...
## Resampling results:
##
## Accuracy Kappa
## 0.6125511 0.1640448
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 0 0
## 1 49 81
##
## Accuracy : 0.6231
## 95% CI : (0.5339, 0.7065)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.539
##
## Kappa : 0
## Mcnemar's Test P-Value : 7.025e-12
##
## Sensitivity : 0.0000
## Specificity : 1.0000
## Pos Pred Value : NaN
## Neg Pred Value : 0.6231
## Prevalence : 0.3769
## Detection Rate : 0.0000
## Detection Prevalence : 0.0000
## Balanced Accuracy : 0.5000
##
## 'Positive' Class : 0
##
Vemos que este clasificador no es en absoluto mejor que el Naive Classifier, es decir, aquél que diría siempre que el Return va a ser positivo; esto lo podemos ver en la matriz de confusión superior, en la cual se aprecia que no hay ninguna observación del test para la que la predicción sea 0. Esto podríamos mejorarlo cambiando las probabilidades a priori que tiene cada clase, de esta forma sesgamos un poco el modelo pero nos equivocaremos menos los días que baje la bolsa, de esta forma perderíamos menos dinero.
## Shrinkage Discriminant Analysis
##
## 429 samples
## 7 predictor
## 2 classes: '0', '1'
##
## Pre-processing: centered (7), scaled (7)
## Resampling: Cross-Validated (5 fold, repeated 10 times)
## Summary of sample sizes: 344, 342, 344, 343, 343, 342, ...
## Resampling results across tuning parameters:
##
## lambda Accuracy Kappa
## 0.0 0.6354171 0.2206779
## 0.5 0.6032406 0.1021078
## 1.0 0.5829337 0.1031499
##
## Tuning parameter 'diagonal' was held constant at a value of FALSE
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were diagonal = FALSE and lambda = 0.
## Prediction uses 7 features.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 0 0
## 1 49 81
##
## Accuracy : 0.6231
## 95% CI : (0.5339, 0.7065)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.539
##
## Kappa : 0
## Mcnemar's Test P-Value : 7.025e-12
##
## Sensitivity : 0.0000
## Specificity : 1.0000
## Pos Pred Value : NaN
## Neg Pred Value : 0.6231
## Prevalence : 0.3769
## Detection Rate : 0.0000
## Detection Prevalence : 0.0000
## Balanced Accuracy : 0.5000
##
## 'Positive' Class : 0
##
Para sesgar al modelo y conseguir que capte algunas bajadas del precio y no se convierta en el Naive Predictor (Aquél que tendría un \(Accuracy = 0.58\) sólo por decir siempre que el precio sube) vamos a poner que las probabilidades a posteriori son 0.9 para el 0 y 0.1 para el 1 (después de probar con varios valores estos han sido los únicos que nos daban un resultado efectivo a la hora de conseguir predecir bien algunos 0).
## Length Class Mode
## prior 2 -none- numeric
## counts 2 -none- numeric
## means 28 -none- numeric
## scaling 14 -none- numeric
## lev 2 -none- character
## svd 1 -none- numeric
## N 1 -none- numeric
## call 4 -none- call
## terms 3 terms call
## xlevels 0 -none- list
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 16 21
## 1 33 60
##
## Accuracy : 0.5846
## 95% CI : (0.4949, 0.6703)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.8403
##
## Kappa : 0.0707
## Mcnemar's Test P-Value : 0.1344
##
## Sensitivity : 0.3265
## Specificity : 0.7407
## Pos Pred Value : 0.4324
## Neg Pred Value : 0.6452
## Prevalence : 0.3769
## Detection Rate : 0.1231
## Detection Prevalence : 0.2846
## Balanced Accuracy : 0.5336
##
## 'Positive' Class : 0
##
Vemos que de esta forma somos capaces de predecir algunos 0 (el precio baja), pero sigue sin ser suficiente; parece que aún no encontramos un modelo que pueda captar bien las tendencias de los precios.
## Quadratic Discriminant Analysis
##
## 429 samples
## 14 predictor
## 2 classes: '0', '1'
##
## Pre-processing: centered (14), scaled (14)
## Resampling: Cross-Validated (5 fold, repeated 10 times)
## Summary of sample sizes: 342, 344, 344, 342, 344, 344, ...
## Resampling results:
##
## Accuracy Kappa
## 0.5797283 0.07732432
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 34 49
## 1 15 32
##
## Accuracy : 0.5077
## 95% CI : (0.4186, 0.5964)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.9972
##
## Kappa : 0.0782
## Mcnemar's Test P-Value : 3.707e-05
##
## Sensitivity : 0.6939
## Specificity : 0.3951
## Pos Pred Value : 0.4096
## Neg Pred Value : 0.6809
## Prevalence : 0.3769
## Detection Rate : 0.2615
## Detection Prevalence : 0.6385
## Balanced Accuracy : 0.5445
##
## 'Positive' Class : 0
##
Vemos que QDA tiene una ventaja sobre LDA en el sentido de que los errores no están todos concentrados en la clase minoritaria, sino que se reparten. Esto significa que también predecimos considerablemente bien cuando el precio del mercado va a bajar. Sin embargo, el Accuracy baja bastante.
## Quadratic Discriminant Analysis
##
## 429 samples
## 7 predictor
## 2 classes: '0', '1'
##
## Pre-processing: centered (7), scaled (7)
## Resampling: Cross-Validated (5 fold, repeated 10 times)
## Summary of sample sizes: 342, 343, 344, 344, 343, 344, ...
## Resampling results:
##
## Accuracy Kappa
## 0.6223565 0.1982167
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 7 9
## 1 42 72
##
## Accuracy : 0.6077
## 95% CI : (0.5182, 0.6921)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.6766
##
## Kappa : 0.0366
## Mcnemar's Test P-Value : 7.433e-06
##
## Sensitivity : 0.14286
## Specificity : 0.88889
## Pos Pred Value : 0.43750
## Neg Pred Value : 0.63158
## Prevalence : 0.37692
## Detection Rate : 0.05385
## Detection Prevalence : 0.12308
## Balanced Accuracy : 0.51587
##
## 'Positive' Class : 0
##
El Kappa en este caso baja algo con respecto a LDA en el test, aunque el Accuracy sube un poco. Lo que parece claro es que con QDA el segundo modelo generaliza mejor que el primero.
## Naive Bayes
##
## 429 samples
## 14 predictor
## 2 classes: '0', '1'
##
## Pre-processing: centered (14), scaled (14)
## Resampling: Cross-Validated (5 fold, repeated 10 times)
## Summary of sample sizes: 344, 343, 344, 342, 343, 342, ...
## Resampling results across tuning parameters:
##
## usekernel Accuracy Kappa
## FALSE 0.5692831 0.02926108
## TRUE 0.5677084 0.09639008
##
## Tuning parameter 'fL' was held constant at a value of 0
## Tuning
## parameter 'adjust' was held constant at a value of 1
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were fL = 0, usekernel = FALSE
## and adjust = 1.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 17 16
## 1 32 65
##
## Accuracy : 0.6308
## 95% CI : (0.5417, 0.7137)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.46691
##
## Kappa : 0.1597
## Mcnemar's Test P-Value : 0.03038
##
## Sensitivity : 0.3469
## Specificity : 0.8025
## Pos Pred Value : 0.5152
## Neg Pred Value : 0.6701
## Prevalence : 0.3769
## Detection Rate : 0.1308
## Detection Prevalence : 0.2538
## Balanced Accuracy : 0.5747
##
## 'Positive' Class : 0
##
## Naive Bayes
##
## 429 samples
## 7 predictor
## 2 classes: '0', '1'
##
## Pre-processing: centered (7), scaled (7)
## Resampling: Cross-Validated (5 fold, repeated 10 times)
## Summary of sample sizes: 342, 343, 343, 344, 344, 344, ...
## Resampling results across tuning parameters:
##
## usekernel Accuracy Kappa
## FALSE 0.5692575 0.05679631
## TRUE 0.5699445 0.10154103
##
## Tuning parameter 'fL' was held constant at a value of 0
## Tuning
## parameter 'adjust' was held constant at a value of 1
## Accuracy was used to select the optimal model using the largest value.
## The final values used for the model were fL = 0, usekernel = TRUE
## and adjust = 1.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 2 1
## 1 47 80
##
## Accuracy : 0.6308
## 95% CI : (0.5417, 0.7137)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.4669
##
## Kappa : 0.035
## Mcnemar's Test P-Value : 8.293e-11
##
## Sensitivity : 0.04082
## Specificity : 0.98765
## Pos Pred Value : 0.66667
## Neg Pred Value : 0.62992
## Prevalence : 0.37692
## Detection Rate : 0.01538
## Detection Prevalence : 0.02308
## Balanced Accuracy : 0.51424
##
## 'Positive' Class : 0
##
Parece que en general funcionan mejor los algoritmos con el segundo modelo que con el primero; esto se puede deber a que el primero sigue teniendo problemas de muticolinearidad pese a haber eliminado ya algunas de las columnas que estaban produciendo esto. Esto en Naive Bayes no queda tan claro, pues en el segundo caso tanto el Kappa es menor que con el primer modelo. Vemos además que Naive Bayes predice algo mejor que los métodos utilizados anteriormente. De todas formas, la comparación de los modelos se realizará más adelante, por lo que ahora no nos centraremos tanto en compararlos.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 0 0
## 1 49 81
##
## Accuracy : 0.6231
## 95% CI : (0.5339, 0.7065)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.539
##
## Kappa : 0
## Mcnemar's Test P-Value : 7.025e-12
##
## Sensitivity : 0.0000
## Specificity : 1.0000
## Pos Pred Value : NaN
## Neg Pred Value : 0.6231
## Prevalence : 0.3769
## Detection Rate : 0.0000
## Detection Prevalence : 0.0000
## Balanced Accuracy : 0.5000
##
## 'Positive' Class : 0
##
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 0 0
## 1 49 81
##
## Accuracy : 0.6231
## 95% CI : (0.5339, 0.7065)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.539
##
## Kappa : 0
## Mcnemar's Test P-Value : 7.025e-12
##
## Sensitivity : 0.0000
## Specificity : 1.0000
## Pos Pred Value : NaN
## Neg Pred Value : 0.6231
## Prevalence : 0.3769
## Detection Rate : 0.0000
## Detection Prevalence : 0.0000
## Balanced Accuracy : 0.5000
##
## 'Positive' Class : 0
##
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 0 0
## 1 49 81
##
## Accuracy : 0.6231
## 95% CI : (0.5339, 0.7065)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.539
##
## Kappa : 0
## Mcnemar's Test P-Value : 7.025e-12
##
## Sensitivity : 0.0000
## Specificity : 1.0000
## Pos Pred Value : NaN
## Neg Pred Value : 0.6231
## Prevalence : 0.3769
## Detection Rate : 0.0000
## Detection Prevalence : 0.0000
## Balanced Accuracy : 0.5000
##
## 'Positive' Class : 0
##
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 0 0
## 1 49 81
##
## Accuracy : 0.6231
## 95% CI : (0.5339, 0.7065)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.539
##
## Kappa : 0
## Mcnemar's Test P-Value : 7.025e-12
##
## Sensitivity : 0.0000
## Specificity : 1.0000
## Pos Pred Value : NaN
## Neg Pred Value : 0.6231
## Prevalence : 0.3769
## Detection Rate : 0.0000
## Detection Prevalence : 0.0000
## Balanced Accuracy : 0.5000
##
## 'Positive' Class : 0
##
En el caso de SVM con Kernel Lineal, vemos que con ninguno de los dos modelos es captar de separar bien la muestra; no predice ningún 0.
Ahora probaremos con otro tipo de Support Vector Machine, con un Kernel Radial, expandiéndole el grid para que pueda optimizar sus hiperparámetros.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 3 3
## 1 46 78
##
## Accuracy : 0.6231
## 95% CI : (0.5339, 0.7065)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.539
##
## Kappa : 0.0293
## Mcnemar's Test P-Value : 1.973e-09
##
## Sensitivity : 0.06122
## Specificity : 0.96296
## Pos Pred Value : 0.50000
## Neg Pred Value : 0.62903
## Prevalence : 0.37692
## Detection Rate : 0.02308
## Detection Prevalence : 0.04615
## Balanced Accuracy : 0.51209
##
## 'Positive' Class : 0
##
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 1 3
## 1 48 78
##
## Accuracy : 0.6077
## 95% CI : (0.5182, 0.6921)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.6766
##
## Kappa : -0.0203
## Mcnemar's Test P-Value : 7.218e-10
##
## Sensitivity : 0.020408
## Specificity : 0.962963
## Pos Pred Value : 0.250000
## Neg Pred Value : 0.619048
## Prevalence : 0.376923
## Detection Rate : 0.007692
## Detection Prevalence : 0.030769
## Balanced Accuracy : 0.491686
##
## 'Positive' Class : 0
##
En el caso de SVM con Kernel Radial, el modelo 1 funciona mejor que el modelo 2, pues como vemos el Kappa es mayor en el primer caso, y distinto de 0, lo cual es un pequeño avance con respecto a la mayoría de métodos que vimos anteriormente.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 9 8
## 1 40 73
##
## Accuracy : 0.6308
## 95% CI : (0.5417, 0.7137)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.4669
##
## Kappa : 0.0975
## Mcnemar's Test P-Value : 7.66e-06
##
## Sensitivity : 0.18367
## Specificity : 0.90123
## Pos Pred Value : 0.52941
## Neg Pred Value : 0.64602
## Prevalence : 0.37692
## Detection Rate : 0.06923
## Detection Prevalence : 0.13077
## Balanced Accuracy : 0.54245
##
## 'Positive' Class : 0
##
Vemos que con el modelo 1 KNN funciona bastante bien, mejor que muchos de los métodos examinados hasta ahora, y mejor por ejemplo que SVM Lineal, que en teoría es un algoritmo más sofisticado.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 2 6
## 1 47 75
##
## Accuracy : 0.5923
## 95% CI : (0.5027, 0.6776)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.793
##
## Kappa : -0.0398
## Mcnemar's Test P-Value : 3.92e-08
##
## Sensitivity : 0.04082
## Specificity : 0.92593
## Pos Pred Value : 0.25000
## Neg Pred Value : 0.61475
## Prevalence : 0.37692
## Detection Rate : 0.01538
## Detection Prevalence : 0.06154
## Balanced Accuracy : 0.48337
##
## 'Positive' Class : 0
##
Sin embargo con el segundo modelo el rendimiento de KNN baja considerablemente, pues el Kappa es incluso negativo. Vemos que en este caso sería mejor por lo tanto utilizar más variables. De nuevo, vemos que los problemas reales de utilizar muchas variables vienen por el lado de la multicolinearidad, y que los algoritmos que no necesitan calcular determinantes de la matriz ni nada por el estilo (en el caso del KNN lo que se hace es calcular una matriz de distancias multidimensional entre los puntos y se le otorga al punto nuevo el valor de lo más “votado” entre los puntos con los que tiene mayor cercanía – como vemos, nada tiene que ver esto con calcular un determinante) se aprovechan de la ganancia de información que viene por el lado de la inclusión de más variables.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 0 0
## 1 49 81
##
## Accuracy : 0.6231
## 95% CI : (0.5339, 0.7065)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.539
##
## Kappa : 0
## Mcnemar's Test P-Value : 7.025e-12
##
## Sensitivity : 0.0000
## Specificity : 1.0000
## Pos Pred Value : NaN
## Neg Pred Value : 0.6231
## Prevalence : 0.3769
## Detection Rate : 0.0000
## Detection Prevalence : 0.0000
## Balanced Accuracy : 0.5000
##
## 'Positive' Class : 0
##
Con las variables del Modelo 1 parece que las redes neuronales planteadas no funcionan demasiado allá, pues como vemos tienen un Kappa de 0. Veamos qué tal funcionan con el Modelo 2.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 0 0
## 1 49 81
##
## Accuracy : 0.6231
## 95% CI : (0.5339, 0.7065)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.539
##
## Kappa : 0
## Mcnemar's Test P-Value : 7.025e-12
##
## Sensitivity : 0.0000
## Specificity : 1.0000
## Pos Pred Value : NaN
## Neg Pred Value : 0.6231
## Prevalence : 0.3769
## Detection Rate : 0.0000
## Detection Prevalence : 0.0000
## Balanced Accuracy : 0.5000
##
## 'Positive' Class : 0
##
Vemos que tampoco mejoran las redes neuronales para el modelo reducido en variables; el Kappa vuelve a ser 0 por lo que vemos que no es un buen modelo para este problema que queremos resolver.
Hay otros modelos que llevan incorporada la selección de variables de una forma u de otra, por ejemplo el Random Forest o el XGB. ¿En qué sentido tienen incorporada la selección de variables? Bien, en el hecho de que no utilizan todas las variables en los árboles de decisión que los forman para realizar los cortes en los datos de cada una de las ramas del árbol; de esta forma, podemos incluso sacar un feature importances que nos indica el impacto teórico de cada variable en el conjunto de árboles que forman el modelo de ensemble a la hora de realizar los cortes. Básicamente esto es un indicador del número de veces (de forma relativa) que ha aparecido cada variable en los cortes de los árboles. De esta forma, a la hora de aplicar modelos de este tipo es posible que los problemas de colinearidad desaparezcan y que tenga más sentido pasarles un modelo con todos los datos; pues de todas formas las variables que no tengan nada que decir sobre el TARGET no aparecerán en los árboles (esto es especialmente cierto en el XGB, en el que cada nuevo estimador (árbol) del ensemble se diseña teniendo en cuenta cómo están construidos y la información que contienen los árboles creados anteriormente).
Creamos por tanto una tercera modalidad de modelo, modfull, para este tipo de métodos de clasificación.
La implementación tanto del Random Forest como del resto de modelos de Ensemble se llevará a cabo en Python utilizando la librería sklearn y XGBoost, que nos permite hacer mejor y más fácilmente el hyperparameter tuning, además de ser mucho más rápido el cálculo. Los resultados de estos dos algoritmos se podrán ver más adelante al realizar las gráficas y demás.
write.csv(train, 'train.csv', sep = ',', col.names=T)
write.csv(test, 'test.csv', sep = ',', col.names=T)A continuación mostraremos la matriz de confusión para Random Forest, XGB y Adaboost.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 0 0
## 1 49 81
##
## Accuracy : 0.6231
## 95% CI : (0.5339, 0.7065)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.539
##
## Kappa : 0
## Mcnemar's Test P-Value : 7.025e-12
##
## Sensitivity : 0.0000
## Specificity : 1.0000
## Pos Pred Value : NaN
## Neg Pred Value : 0.6231
## Prevalence : 0.3769
## Detection Rate : 0.0000
## Detection Prevalence : 0.0000
## Balanced Accuracy : 0.5000
##
## 'Positive' Class : 0
##
El Random Forest curiosamente (digo curiosamente porque es uno de los algoritmos que mejor funcionan normalmente a la hora de predecir) tiene un Kappa de 0, es decir, que no es mejor que los métodos más simples de clasificación que vimos anteriormente.
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 14 27
## 1 35 54
##
## Accuracy : 0.5231
## 95% CI : (0.4337, 0.6114)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.9921
##
## Kappa : -0.0492
## Mcnemar's Test P-Value : 0.3740
##
## Sensitivity : 0.2857
## Specificity : 0.6667
## Pos Pred Value : 0.3415
## Neg Pred Value : 0.6067
## Prevalence : 0.3769
## Detection Rate : 0.1077
## Detection Prevalence : 0.3154
## Balanced Accuracy : 0.4762
##
## 'Positive' Class : 0
##
XGBoost, otro de los modelos más utilizados para predicción (pues normalmente su capacidad predictiva es alta), tiene una puntuación negativa en el Kappa score…
## Confusion Matrix and Statistics
##
## Reference
## Prediction 0 1
## 0 7 13
## 1 42 68
##
## Accuracy : 0.5769
## 95% CI : (0.4872, 0.663)
## No Information Rate : 0.6231
## P-Value [Acc > NIR] : 0.8797927
##
## Kappa : -0.02
## Mcnemar's Test P-Value : 0.0001597
##
## Sensitivity : 0.14286
## Specificity : 0.83951
## Pos Pred Value : 0.35000
## Neg Pred Value : 0.61818
## Prevalence : 0.37692
## Detection Rate : 0.05385
## Detection Prevalence : 0.15385
## Balanced Accuracy : 0.49118
##
## 'Positive' Class : 0
##
Y resulta que Adaboost, otro modelo de Ensemble, también tiene un Kappa por debajo de 0. Parece, por tanto, que ninguno de los modelos de clasificación avanzada utilizados es realmente útil a la hora de predecir el signo del retorno del SP500 en el siguiente mes.
En esta sección, nos centraremos en analizar los resultados de todos los modelos de forma visual, de tal forma que podremos quedarnos con uno o dos de ellos para la parte final, en la cual diseñaremos una estrategia de Trading para el SP500.
Vemos que curiosamente KNN, uno de los algoritmos más simples, con el Modelo 1 (el que tiene muchas de las variables incluidas, solo con algunas ausencias) es el que mejor predice en el test con respecto a la predicción “Naive” o “tonta”. Como mencionamos antes, hemos utilizado el Kappa en lugar de Accuracy porque es una medida que contrasta el Accuracy obtenido por el modelo con el Accuracy que se obtendría de forma espúrica digamos. De esta forma tenemos algunos modelos como Naive Bayes con el Modelo 1 o XGB que tienen Kappa negativo (su accuracy está por debajo de lo que cabría esperar de una predicción tonta). Después de KNN con el Modelo 1, el siguiente que mejor funciona es QDA con el modelo 1, y después LDA Bis con el Modelo 1. Podríamos, por tanto, coger estos tres modelos para la fase final, es decir la de selección de una estrategia de trading para el SP500. Podríamos incluso utilizar los 3 modelos al mismo tiempo, cogiendo el voto mayoritario en cada caso de las 3 predicciones. De momento cogemos estos 3 modelos:
KNN1: KNN con las variables del Modelo 1
QDA1: QDA con las variables del Modelo 1
LDA1: LDA (Bis, ayudándole con las probabilidades a priori, sesgándole) con las variables del Modelo 1.
En términos de Accuracy, vemos que la mayoría de los modelos tienen una puntuación muy parecida, de un Accuracy de en torno al 60%. Vemos que modelos que en términos de Kappa no predecían bien, como pueden ser ambos Naive Bayes (dos de las puntuaciones más altas en términos de Accuracy) o Random Forest, que en Kappa tenía 0 y en términos de accuracy vemos que no queda muy por detrás del mejor modelo, KNN1.
En este apartado trateremos de desarrollar una estrategia lucrativa de trading con el SP500. Para ello utilizaremos los 3 mejores algoritmos en términos de Kappa que vimos en el apartado anterior. Para ello utilizaremos sus predicciones de probabilidad, en lugar del “TARGET” predicho como tal (queremos utilizar la probabilidad estimada por el modelo de que el mercado suba); lo que haremos será buscar el margen necesario para que dicha estrategia de trading sea lucrativa. En este apartado sí mostraremos la mayoría del código utilizado, pues puede resultar algo confuso de explicar en palabras (cosa que también haremos), y poder ver el código ayudará a la comprensión de las reglas que seguirá nuestra estrategia de inversión. Para esta sección, asumiremos que podemos ir tanto en largo (posición = 1) como en corto (posición = -1).
Para calcular el cumulative return, lo haremos descontando la tasa libre de riesgo (los bonos del tesoro americano), es decir, teniendo en cuenta el coste de oportunidad de la actividad financiera. Para ello utilizaremos la columna “CRSP_SPvwx”. Sin embargo, no tenemos en cuenta los costes en términos de intereses de ir en corto, así como las comisiones de mercado, pues ambos los desconocemos y no tenemos una forma fácil de estimarlo. Para un análisis más completo de lo lucrativa que podría resultar cualquiera de estas estrategias puestas en práctica, tendríamos que tener en cuenta ambos costes.
tresholds = c(0.05, 0.10, 0.15, 0.20, 0.25, 0.3, 0.35, 0.4, 0.45)Para poder contrastar lo buenos que son los modelos a la hora de generar beneficios, los contrastaremos con la estrategia Buy&Hold, que veremos a continuación.
Vamos a visualizar la estrategia Buy&Hold, para poder comparar nuestros resultados con lo que habría supuesto utilizar un modelo para predecir y hacer trading.
Como podemos ver, el Buy&Hold Strategy da un rendimiento bastante pobre, por lo que es posible que varios de nuestros modelos lo superen.
En este sub-apartado, lo que haremos será simular una situación real en la que empezaremos a hacer trading a partir del momento en el que empieza el test. Recordemos que el momento de test empezaba el 1 de Febrero de 2006.
d3$date = dates
d3$CRSP_SPvwx = returns
which(d3$date=="2006-02-01")## [1] 430
nrow(d3[430:nrow(d3), ])## [1] 130
testdates = d3[430:nrow(d3), 'date']CumRetDf = matrix(nrow=130, ncol=length(tresholds))
colnames(CumRetDf) = tresholds
j = 0
pos = 0
for(treshold in tresholds){
j = j + 1
nbuy=0
nsold=0
cumret = c(0)
for(i in 430:(nrow(d3) - 1)){
train = d3[1:i-1, ]
train$TARGET = factor(train$TARGET)
test = d3[i, ]
knn <- train(mod1, method = "knn",
data = train,
preProcess = c("center", "scale"),
trControl = ctrl)
knnpred = predict(knn, test, type="prob")[, 2]
if(knnpred > (0.5 + treshold)){
#print(paste("time to buy at date", as.character(d3[i, "date"])))
pos = 1
ret = pos*d3[i + 1, "CRSP_SPvwx"]
retcum = cumret[length(cumret)] + ret
cumret = c(cumret, retcum)
nbuy= nbuy + 1
#} else if(knnpred <= (0.5 + treshold) & pos >= 0){
} else {
#print(paste("time to sell at date", as.character(d3[i, "date"])))
nsold = nsold + 1
pos = -1
ret = pos*d3[i+1, "CRSP_SPvwx"]
retcum = cumret[length(cumret)] + ret
cumret = c(cumret, retcum)
}
}
print(paste("Con treshold ", as.character(treshold)," he vendido ", as.character(nsold), " veces, y he comprado ", as.character(nbuy), " veces"))
CumRetDf[ , j] = cumret
}## [1] "Con treshold 0.05 he vendido 17 veces, y he comprado 112 veces"
## [1] "Con treshold 0.1 he vendido 51 veces, y he comprado 78 veces"
## [1] "Con treshold 0.15 he vendido 53 veces, y he comprado 76 veces"
## [1] "Con treshold 0.2 he vendido 81 veces, y he comprado 48 veces"
## [1] "Con treshold 0.25 he vendido 88 veces, y he comprado 41 veces"
## [1] "Con treshold 0.3 he vendido 111 veces, y he comprado 18 veces"
## [1] "Con treshold 0.35 he vendido 111 veces, y he comprado 18 veces"
## [1] "Con treshold 0.4 he vendido 125 veces, y he comprado 4 veces"
## [1] "Con treshold 0.45 he vendido 125 veces, y he comprado 4 veces"
rets_knn = data.frame(CumRetDf)
rets_knn$date = testdates
gathered_knn = melt(rets_knn, id="date")
names(gathered_knn)[2] = "Treshold"
p = ggplot(gathered_knn, aes(x = date, y = value, group = Treshold, colour = Treshold)) +
geom_line() +
theme(axis.text.x = element_text(angle=60, vjust=0.5)) +
ylab('Cumulative Return') +
ggtitle("Cumulative Return for KNN with different Tresholds")
pParece bastante claro que lo mejor en este caso sería utilizar un treshold de 0.05, es decir, que si la predicción es que la probabilidad de que el precio suba es de más de un 55%, se compra el activo, y sino se vende. La rentabilidad es negativa, como vemos, por lo tanto una estrategia de trading basada en este algoritmo no creemos que pudiera funcionar. Un algoritmo para tener suficiente rentabilidad en la vida real debería tener un margen enorme en este test; pues parte del mismo se lo van a “comer” las comisiones y los intereses al ir en corto.
CumRetDf = matrix(nrow=130, ncol=length(tresholds))
colnames(CumRetDf) = tresholds
j = 0
pos = 0
for(treshold in tresholds){
j = j + 1
nbuy=0
nsold=0
cumret = c(0)
for(i in 430:(nrow(d3) - 1)){
train = d3[1:i-1, ]
train$TARGET = factor(train$TARGET)
test = d3[i, ]
qda <- train(mod1,
# method = "lda", "lda2", "rda",
method = "qda",
data = train,
preProcess = c("center", "scale"),
trControl = ctrl)
qdapred = predict(qda, test, type="prob")[, 2]
if(qdapred > (0.5 + treshold)){
#print(paste("time to buy at date", as.character(d3[i, "date"])))
pos = 1
ret = pos*d3[i + 1, "CRSP_SPvwx"]
retcum = cumret[length(cumret)] + ret
cumret = c(cumret, retcum)
nbuy= nbuy + 1
#} else if(knnpred <= (0.5 + treshold) & pos >= 0){
} else {
#print(paste("time to sell at date", as.character(d3[i, "date"])))
nsold = nsold + 1
pos = -1
ret = pos*d3[i+1, "CRSP_SPvwx"]
retcum = cumret[length(cumret)] + ret
cumret = c(cumret, retcum)
}
}
print(paste("Con treshold ", as.character(treshold)," he vendido ", as.character(nsold), " veces, y he comprado ", as.character(nbuy), " veces"))
CumRetDf[ , j] = cumret
}## [1] "Con treshold 0.05 he vendido 20 veces, y he comprado 109 veces"
## [1] "Con treshold 0.1 he vendido 22 veces, y he comprado 107 veces"
## [1] "Con treshold 0.15 he vendido 26 veces, y he comprado 103 veces"
## [1] "Con treshold 0.2 he vendido 30 veces, y he comprado 99 veces"
## [1] "Con treshold 0.25 he vendido 34 veces, y he comprado 95 veces"
## [1] "Con treshold 0.3 he vendido 45 veces, y he comprado 84 veces"
## [1] "Con treshold 0.35 he vendido 53 veces, y he comprado 76 veces"
## [1] "Con treshold 0.4 he vendido 67 veces, y he comprado 62 veces"
## [1] "Con treshold 0.45 he vendido 84 veces, y he comprado 45 veces"
rets_qda = data.frame(CumRetDf)
rets_qda$date = testdates
gathered_qda = melt(rets_qda, id="date")
names(gathered_qda)[2] = "Treshold"
p = ggplot(gathered_qda, aes(x = date, y = value, group = Treshold, colour = Treshold)) +
geom_line() +
theme(axis.text.x = element_text(angle=60, vjust=0.5)) +
ylab('Cumulative Return') +
ggtitle("Cumulative Return for QDA1 with different Tresholds") +
labs(caption="A VACA ON DATA (alexvaca0.github.io)")
ggplotly(p)Sin embargo parece que QDA1 es capaz de hacer muy buenos cumulative return con diferentes tresholds, siendo todas sus apuestas ganadoras (como vemos, todas las estrategias, sea cual sea el treshold, están por encima del 0% del cumulative return, en contraste con las del modelo anterior). De nuevo el mejor Treshold parece ser 0.05.
CumRetDf = matrix(nrow=130, ncol=length(tresholds))
colnames(CumRetDf) = tresholds
j = 0
pos = 0
for(treshold in tresholds){
j = j + 1
cumret = c(0)
nbuy=0
nsold=0
for(i in 430:(nrow(d3) - 1)){
train = d3[1:i-1, ]
train$TARGET = factor(train$TARGET)
test = d3[i, ]
lda = lda(mod1, data = train, prior = c(0.9, 0.1))
ldapred = predict(lda, test)$posterior[,2]
if(ldapred > (0.5 + treshold)){
#print(paste("time to buy at date", as.character(d3[i, "date"])))
pos = 1
ret = pos*d3[i + 1, "CRSP_SPvwx"]
retcum = cumret[length(cumret)] + ret
cumret = c(cumret, retcum)
nbuy= nbuy + 1
#} else if(knnpred <= (0.5 + treshold) & pos >= 0){
} else {
#print(paste("time to sell at date", as.character(d3[i, "date"])))
nsold = nsold + 1
pos = -1
ret = pos*d3[i+1, "CRSP_SPvwx"]
retcum = cumret[length(cumret)] + ret
cumret = c(cumret, retcum)
}
}
print(paste("Con treshold ", as.character(treshold)," he vendido ", as.character(nsold), " veces, y he comprado ", as.character(nbuy), " veces"))
CumRetDf[ , j] = cumret
}## [1] "Con treshold 0.05 he vendido 128 veces, y he comprado 1 veces"
## [1] "Con treshold 0.1 he vendido 129 veces, y he comprado 0 veces"
## [1] "Con treshold 0.15 he vendido 129 veces, y he comprado 0 veces"
## [1] "Con treshold 0.2 he vendido 129 veces, y he comprado 0 veces"
## [1] "Con treshold 0.25 he vendido 129 veces, y he comprado 0 veces"
## [1] "Con treshold 0.3 he vendido 129 veces, y he comprado 0 veces"
## [1] "Con treshold 0.35 he vendido 129 veces, y he comprado 0 veces"
## [1] "Con treshold 0.4 he vendido 129 veces, y he comprado 0 veces"
## [1] "Con treshold 0.45 he vendido 129 veces, y he comprado 0 veces"
rets_lda = data.frame(CumRetDf)
rets_lda$date = testdates
gathered_lda = melt(rets_lda, id="date")
names(gathered_lda)[2] = "Treshold"
p = ggplot(gathered_lda, aes(x = date, y = value, group = Treshold, colour = Treshold)) +
geom_line() +
theme(axis.text.x = element_text(angle=60, vjust=0.5)) +
ylab('Cumulative Return') +
ggtitle("Cumulative Return for LDA with different Tresholds") +
labs(caption="A VACA ON DATA (alexvaca0.github.io)")
ggplotly(p)El motivo por el que no vemos la mayoría de puntos es porque están solapados por los demás, es decir, la mayoría de Tresholds dan un resultado bastante similar, mostrando que el Kappa mayor de este modelo no tenía nada que ver con una variedad adecuada en las predicciones en contraposición con los dos modelos mostrados anteriormente. Todos los modelos acaban con un cumulative return negativo. El que menos pierde es el LDA 1 con el Treshold en 0.4.
total_returns = data.frame(date=testdates, knn=rets_knn$X0.05, qda=rets_qda$X0.05, lda=rets_lda$X0.4, buyhold=cum_ret)
gath_rets = melt(total_returns, id="date")
names(gath_rets)[2] = "Strategy"
p = ggplot(gath_rets, aes(x = date, y = value, colour = Strategy, group=Strategy)) +
geom_line() +
theme(axis.text.x = element_text(angle=60, vjust=0.5)) +
ylab('Cumulative Return') +
ggtitle("Cumulative Return for the Different Strategies") +
labs(caption="A VACA ON DATA (alexvaca0.github.io)")
ggplotly(p)Como vemos en el gráfico superior,como QDA (este con mucha más claridad) supera el Buy&Hold Strategy. Podemos decir, pues, que haber utilizado QDA1 entre los años 2006 y 2017 (casi, Noviembre de 2016) habría sido lucrativo, al menos más que el mercado. Vemos de esta forma que se puede encontrar un modelo que dé unos resultados bastante aceptables para clasificar si sube o baja el precio, de mes a mes, del SP500, el cual podríamos comprar a través de un ETF o algún otro instrumento que trackee índices. El mercado de estos productos es líquido y por lo tanto podríamos establecer una estrategia de trading exitosa, vistos los resultados, con las limitaciones de no haber tenido en cuenta los costes de operar en el mercado, los cuales, de forma porcentual, esperamos que sea inferior a la diferencia que existe entre el Cumulative Return del QDA y el Cumulative Return del Buy&Hold. Estos costes tendrían una clara influencia sobre el treshold que hemos optimizado en esta última parte, ya que tendríamos que estar más seguros de que va a bajar el precio a la hora de ir en corto que de que va a subir para ir con posición = 1.